﻿using System;
using UnityEngine;
using System.Collections.Generic;
using UnityEngine.UI;
using UnityEngine.Events;
using UnityEngine.SocialPlatforms;

[RequireComponent(typeof(ScrollRect))]
public class ListViewH : MonoBehaviour
{

    #region Public API

    public Dictionary<int, ListViewCellH> VisibleCells => m_visibleCells;
    public ListViewControllerH dataSource
    {
        get { return m_dataSource; }
        set { m_dataSource = value; m_requiresReload = true; }
    }

    [System.Serializable]
    public class CellVisibilityChangeEvent : UnityEvent<int, bool> { }

    [HideInInspector]
    public CellVisibilityChangeEvent onCellVisibilityChanged;

    public ListViewCellH GetReusableCell(string reuseIdentifier)
    {
        LinkedList<ListViewCellH> cells;
        if (!m_reusableCells.TryGetValue(reuseIdentifier, out cells))
        {
            return null;
        }
        if (cells.Count == 0)
        {
            return null;
        }
        ListViewCellH cell = cells.First.Value;
        cells.RemoveFirst();
        return cell;
    }

    public bool isEmpty { get; private set; }

    public void ReloadData()
    {
        //UnityEngine.Profiling.Profiler.BeginSample("TableView.ReloadData for dataSource:" + m_dataSource.GetType().Name, this);

        if (m_HorizontalLayoutGroup == null)
        {
            Debug.LogError("Horizontal Layout Group is null");
            return;
        }
        m_rowWidths = new float[m_dataSource.GetNumberOfRowsForTableView(this)];
        this.isEmpty = m_rowWidths.Length == 0;
        ClearAllColumns();
        if (this.isEmpty)
        {
            return;
        }
        m_cumulativeColumnWidths = new float[m_rowWidths.Length];
        m_cleanCumulativeIndex = -1;

        for (int i = 0; i < m_rowWidths.Length; i++)
        {
            var cellSize = m_dataSource.GetWidthForColumnInTableView(this, i);
            m_rowWidths[i] = cellSize.x; //TODO这里要改横竖兼容
            if (i > 0)
            {
                m_rowWidths[i] += m_HorizontalLayoutGroup.spacing;
            }
        }
        var y = m_scrollRect.content.sizeDelta[1];
        var x = GetCumulativeColumnWidth(m_rowWidths.Length - 1) + m_HorizontalLayoutGroup.padding.horizontal;
        m_scrollRect.content.sizeDelta = new Vector2(x, y);

        RecalculateVisibleColumnsFromScratch();
        m_requiresReload = false;

        //UnityEngine.Profiling.Profiler.EndSample();
        dataSource.OnTableViewLoaded();

    }

    public ListViewCellH GetCellAtColumn(int col)
    {
        ListViewCellH retVal = null;
        m_visibleCells.TryGetValue(col, out retVal);
        return retVal;
    }

    public Range visibleColumnRange
    {
        get { return m_visiblColumnRange; }
    }

    public void NotifyCellDimensionsChanged(int column)
    {
        float oldWidth = m_rowWidths[column];
        var cellSize = m_dataSource.GetWidthForColumnInTableView(this, column); //TODO这里要改横竖兼容
        m_rowWidths[column] = cellSize.x;
        if (column > 0)
        {
            m_rowWidths[column] += m_HorizontalLayoutGroup.spacing;
        }
        m_cleanCumulativeIndex = Mathf.Min(m_cleanCumulativeIndex, column - 1);
        ListViewCellH cell = GetCellAtColumn(column);
        if (cell != null)
        {
            cell.GetComponent<LayoutElement>().preferredWidth = m_rowWidths[column];
            if (column > 0)
            {
                cell.GetComponent<LayoutElement>().preferredWidth -= m_HorizontalLayoutGroup.spacing;
            }
        }
        float widthDelta = m_rowWidths[column] - oldWidth;
        var content = m_scrollRect.content;
        var contentSizeDelta = content.sizeDelta;
        var x = contentSizeDelta.x + widthDelta;
        var y = contentSizeDelta.y;
        content.sizeDelta = new Vector2(x, y);
        m_requiresRefresh = true;
    }

    public float scrollableWidth
    {
        get
        {
            return m_scrollRect.content.rect.width - (this.transform as RectTransform).rect.width;
        }
    }

    public float scrollX
    {
        get
        {
            return m_scrollX;
        }
        set
        {
            if (this.isEmpty)
            {
                return;
            }
            value = Mathf.Clamp(value, 0, GetScrollXForColumn(m_rowWidths.Length - 1, true));
            if (m_scrollX != value)
            {
                m_scrollX = value;
                m_requiresRefresh = true;
                float relativeScroll = value / this.scrollableWidth;
                m_scrollRect.horizontalNormalizedPosition = relativeScroll;
            }
        }
    }

    public float GetScrollXForColumn(int column, bool above)
    {
        float retVal = GetCumulativeColumnWidth(column);
        retVal += m_HorizontalLayoutGroup.padding.left;
        if (above)
        {
            retVal -= m_rowWidths[column];
        }
        return retVal;
    }

    #endregion

    #region Private implementation

    private ListViewControllerH m_dataSource;
    private bool m_requiresReload;

    private HorizontalLayoutGroup m_HorizontalLayoutGroup;

    private ScrollRect m_scrollRect;
    private LayoutElement m_leftContentPlaceHolder;
    private LayoutElement m_rightContentPlaceholder;

    private float[] m_rowWidths;
    //cumulative[i] = sum(rowHeights[j] for 0 <= j <= i)
    private float[] m_cumulativeColumnWidths;
    private int m_cleanCumulativeIndex;

    private Dictionary<int, ListViewCellH> m_visibleCells;
    private Range m_visiblColumnRange;

    private RectTransform m_reusableCellContainer;
    private Dictionary<string, LinkedList<ListViewCellH>> m_reusableCells;

    private float m_scrollX;

    private bool m_requiresRefresh;

    private void ScrollViewValueChanged(Vector2 newScrollValue)
    {
        float relativeScrollX = newScrollValue.x;
        m_scrollX = relativeScrollX * scrollableWidth;
        m_requiresRefresh = true;

        //Debug.Log(m_scrollY.ToString(("0.00")));
    }

    private void RecalculateVisibleColumnsFromScratch()
    {
        ClearAllColumns();
        SetInitialVisibleColumns();
    }

    private void ClearAllColumns()
    {
        while (m_visibleCells.Count > 0)
        {
            HideColumn(false);
        }
        m_visiblColumnRange = new Range(0, 0);
    }

    void Awake()
    {
        isEmpty = true;
        m_scrollRect = GetComponent<ScrollRect>();
        m_HorizontalLayoutGroup = GetComponentInChildren<HorizontalLayoutGroup>();
        m_leftContentPlaceHolder = CreateEmptyContentPlaceHolderElement("LeftContentPlaceHolder");
        m_leftContentPlaceHolder.transform.SetParent(m_scrollRect.content, false);
        m_rightContentPlaceholder = CreateEmptyContentPlaceHolderElement("RightContentPlaceHolder");
        m_rightContentPlaceholder.transform.SetParent(m_scrollRect.content, false);
        m_visibleCells = new Dictionary<int, ListViewCellH>();

        m_reusableCellContainer = new GameObject("ReusableCells", typeof(RectTransform)).GetComponent<RectTransform>();
        m_reusableCellContainer.SetParent(this.transform, false);
        m_reusableCellContainer.gameObject.SetActive(false);
        m_reusableCells = new Dictionary<string, LinkedList<ListViewCellH>>();
    }

    void Update()
    {
        if (m_requiresReload)
        {
            ReloadData();
        }
    }

    void LateUpdate()
    {
        if (m_requiresRefresh)
        {
            RefreshVisibleColumns();
        }
    }

    void OnEnable()
    {
        m_scrollRect.onValueChanged.AddListener(ScrollViewValueChanged);
    }

    void OnDisable()
    {
        m_scrollRect.onValueChanged.RemoveListener(ScrollViewValueChanged);
    }

    private Range CalculateCurrentVisibleColumnRange()
    {

        float startX = Math.Max(m_scrollX - m_HorizontalLayoutGroup.padding.left, 0);

        var visibleLeftPadding = Math.Max(m_HorizontalLayoutGroup.padding.left - m_scrollX, 0);
        float endX = startX + (this.transform as RectTransform).rect.width - visibleLeftPadding;

        int startIndex = FindIndexOfColumnAtX(startX);
        int endIndex = FindIndexOfColumnAtX(endX);

        return new Range(startIndex, endIndex - startIndex + 1);
    }

    private void SetInitialVisibleColumns()
    {
        Range visibleColumns = CalculateCurrentVisibleColumnRange();
        for (int i = 0; i < visibleColumns.count; i++)
        {
            AddColumn(visibleColumns.from + i, true);
        }
        m_visiblColumnRange = visibleColumns;
        UpdatePaddingElements();
    }

    private void AddColumn(int column, bool atEnd)
    {
        ListViewCellH newCell = m_dataSource.GetCellForColumnInTableView(this, column);
        newCell.transform.SetParent(m_scrollRect.content, false);

        LayoutElement layoutElement = newCell.GetComponent<LayoutElement>();
        if (layoutElement == null)
        {
            layoutElement = newCell.gameObject.AddComponent<LayoutElement>();
        }
        layoutElement.preferredWidth = m_rowWidths[column];
        if (column > 0)
        {
            layoutElement.preferredWidth -= m_HorizontalLayoutGroup.spacing;
        }

        m_visibleCells[column] = newCell;
        if (atEnd)
        {
            newCell.transform.SetSiblingIndex(m_scrollRect.content.childCount - 2); //One before bottom padding
        }
        else
        {
            newCell.transform.SetSiblingIndex(1); //One after the top padding
        }
        this.onCellVisibilityChanged.Invoke(column, true);
        m_dataSource.OnUpdateCell(newCell);
    }

    private void RefreshVisibleColumns()
    {
        m_requiresRefresh = false;

        if (this.isEmpty)
        {
            return;
        }

        Range newVisibleColumns = CalculateCurrentVisibleColumnRange();
        int oldTo = m_visiblColumnRange.Last();
        int newTo = newVisibleColumns.Last();

        if (newVisibleColumns.from > oldTo || newTo < m_visiblColumnRange.from)
        {
            RecalculateVisibleColumnsFromScratch();
            return;
        }

        for (int i = m_visiblColumnRange.from; i < newVisibleColumns.from; i++)
        {
            HideColumn(false);
        }
        for (int i = newTo; i < oldTo; i++)
        {
            HideColumn(true);
        }
        for (int i = m_visiblColumnRange.from - 1; i >= newVisibleColumns.from; i--)
        {
            AddColumn(i, false);
        }
        for (int i = oldTo + 1; i <= newTo; i++)
        {
            AddColumn(i, true);
        }
        m_visiblColumnRange = newVisibleColumns;
        UpdatePaddingElements();
    }

    private void UpdatePaddingElements()
    {
        float hiddenElementsWidthSum = 0;

        for (int i = 0; i < m_visiblColumnRange.from; i++)
        {
            hiddenElementsWidthSum += m_rowWidths[i];
        }
        var leftContentPlaceHolderWidth = hiddenElementsWidthSum;
        m_leftContentPlaceHolder.preferredWidth = leftContentPlaceHolderWidth;
        m_leftContentPlaceHolder.gameObject.SetActive(m_leftContentPlaceHolder.preferredWidth > 0);
        for (int i = m_visiblColumnRange.from; i <= m_visiblColumnRange.Last(); i++)
        {
            hiddenElementsWidthSum += m_rowWidths[i];
        }
        float rightContentPlaceHolderWidth = m_scrollRect.content.rect.width - hiddenElementsWidthSum;
        rightContentPlaceHolderWidth -= m_HorizontalLayoutGroup.padding.left;
        rightContentPlaceHolderWidth -= m_HorizontalLayoutGroup.padding.right;
        m_rightContentPlaceholder.preferredWidth = rightContentPlaceHolderWidth - m_HorizontalLayoutGroup.spacing;
        m_rightContentPlaceholder.gameObject.SetActive(m_rightContentPlaceholder.preferredHeight > 0);
    }

    private void HideColumn(bool last)
    {
        //Debug.Log("Hiding row at scroll y " + m_scrollY.ToString("0.00"));

        int column = last ? m_visiblColumnRange.Last() : m_visiblColumnRange.from;
        ListViewCellH removedCell = m_visibleCells[column];
        StoreCellForReuse(removedCell);
        m_visibleCells.Remove(column);
        m_visiblColumnRange.count -= 1;
        if (!last)
        {
            m_visiblColumnRange.from += 1;
        }
        this.onCellVisibilityChanged.Invoke(column, false);
    }

    private LayoutElement CreateEmptyContentPlaceHolderElement(string name)
    {
        GameObject go = new GameObject(name, typeof(RectTransform), typeof(LayoutElement));
        LayoutElement le = go.GetComponent<LayoutElement>();
        return le;
    }

    private int FindIndexOfColumnAtX(float x)
    {
        //TODO : 二分搜索如果在干净的累积行高区域内，否则一直走到找到为止
        return FindIndexOfColumnAtX(x, 0, m_cumulativeColumnWidths.Length - 1);
    }

    private int FindIndexOfColumnAtX(float x, int startIndex, int endIndex)
    {
        if (startIndex >= endIndex)
        {
            return startIndex;
        }
        int midIndex = (startIndex + endIndex) / 2;
        if (GetCumulativeColumnWidth(midIndex) >= x)
        {
            return FindIndexOfColumnAtX(x, startIndex, midIndex);
        }
        else
        {
            return FindIndexOfColumnAtX(x, midIndex + 1, endIndex);
        }
    }

    private float GetCumulativeColumnWidth(int column)
    {
        while (m_cleanCumulativeIndex < column)
        {
            m_cleanCumulativeIndex++;
            //Debug.Log("Cumulative index : " + m_cleanCumulativeIndex.ToString());
            m_cumulativeColumnWidths[m_cleanCumulativeIndex] = m_rowWidths[m_cleanCumulativeIndex];
            if (m_cleanCumulativeIndex > 0)
            {
                m_cumulativeColumnWidths[m_cleanCumulativeIndex] += m_cumulativeColumnWidths[m_cleanCumulativeIndex - 1];
            }
        }
        return m_cumulativeColumnWidths[column];
    }

    private void StoreCellForReuse(ListViewCellH cell)
    {
        dataSource.OnCloseCell(cell);
        string reuseIdentifier = cell.reuseIdentifier;

        if (string.IsNullOrEmpty(reuseIdentifier))
        {
            GameObject.Destroy(cell.gameObject);
            return;
        }

        if (!m_reusableCells.ContainsKey(reuseIdentifier))
        {
            m_reusableCells.Add(reuseIdentifier, new LinkedList<ListViewCellH>());
        }
        m_reusableCells[reuseIdentifier].AddLast(cell);
        cell.transform.SetParent(m_reusableCellContainer, false);
    }

    #endregion



}

//internal static class RangeExtensions
//{
//    public static int Last(this Range range)
//    {
//        if (range.count == 0)
//        {
//            throw new System.InvalidOperationException("Empty range has no to()");
//        }
//        return (range.from + range.count - 1);
//    }

//    public static bool Contains(this Range range, int num)
//    {
//        return num >= range.from && num < (range.from + range.count);
//    }
//}
